﻿using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;

namespace ChessKit.Logic
{
	/// <summary>Identifies chess board cell coordinates in a user-friendly manner</summary>
	/// <remarks>http://en.wikipedia.org/wiki/Chess</remarks>
	public partial struct Square
	{
		#region ' Properties '

		/// <summary>0..127 index of square</summary>
		public byte Index { get; private set; }
		/// <summary>0..63 number of square</summary>
		public int Number { get { return IntFile*8 + IntRank; } }
		/// <summary>File of the square  (file is: a..h)</summary>
		public char File
		{
			get { return (char)(IntFile + 'a'); }
		}
		/// <summary>Rank of the square (rank is: 1..8)</summary>
		public int Rank
		{
			get { return IntRank + 1; }
		}

		/// <summary>Zero based File of the square (file is: a..h)</summary>
		public int IntFile
		{
			get { return Index & 7; }
		}

		/// <summary>Zero based Rank of the square (rank is: 1..8)</summary>
		public int IntRank
		{
			get { return Index >> 4; }
		}

		#endregion

		#region ' Ctors '

		/// <summary>ctor</summary>
		public Square(int index)
			: this()
		{
			if ((index & 0x88) != 0) throw new ArgumentOutOfRangeException("index");
			Index = (byte)index;
		}

		/// <summary>ctor</summary>
		public Square(byte index)
			: this()
		{
			Index = index;
		}

		/// <summary>ctor</summary>
		/// <param name="file">(file is: a..h)</param>
		/// <param name="rank">(rank is: 1..8)</param>
		public Square(char file, int rank)
			: this()
		{
			if (file < 'a' || file > 'h') throw new ArgumentOutOfRangeException("file");
			if (rank < 1 || rank > 8) throw new ArgumentOutOfRangeException("rank");
			Index = (byte)((file - 'a') + (rank - 1) * 16);
		}

		/// <summary>ctor</summary>
		/// <param name="file">zero base file (file is: a..h)</param>
		/// <param name="rank">zero based rank (rank is: 1..8)</param>
		public Square(int file, int rank)
			: this()
		{
			if (file < 0 || file > 7) throw new ArgumentOutOfRangeException("file");
			if (rank < 0 || rank > 7) throw new ArgumentOutOfRangeException("rank");
			Index = (byte)(file + rank * 16);
		}

		#endregion

		#region ' Parse '

		/// <summary>Parse string into Square</summary>
		/// <param name="square">"a1" or "A1"</param>
		public static Square Parse(string square)
		{
			if (square == null) throw new ArgumentNullException("Square");
			if (square.Length != 2) throw new ArgumentOutOfRangeException("Square");
			var x = Char.ToLower(square[0], CultureInfo.InvariantCulture) - 'a';
			var y = Int32.Parse(square[1].ToString(), CultureInfo.InvariantCulture) - 1;
			if (x < 0 || x > 7) throw new ArgumentOutOfRangeException("Square");
			if (y < 0 || y > 7) throw new ArgumentOutOfRangeException("Square");
			return new Square(x, y);
		}
		/// <summary>Try parse string into Square</summary>
		public static bool TryParse(string square, out Square result)
		{
			if (square == null) throw new ArgumentNullException("Square");
			result = new Square();
			if (square.Length != 2) return false;
			var x = char.ToLower(square[0], CultureInfo.InvariantCulture) - 'a';
			int y;
			if (!int.TryParse(square[1].ToString(),
			  NumberStyles.Integer,
			  CultureInfo.InvariantCulture, out y))
				return false;
			y--;
			if (x < 0 || x > 7) return false;
			if (y < 0 || y > 7) return false;
			result = new Square(x, y);
			return true;
		}
		/// <summary>Gets user friendly transcription of the Square ("a1")</summary>
		public override string ToString()
		{
			return File + Rank.ToString();
		}

		#endregion

		#region  ' Equality '

		/// <summary>Indicates whether this instance and a specified object are equal.</summary>
		public bool Equals(Square other)
		{
			return other.Index == Index;
		}
		/// <summary>Indicates whether this instance and a specified object are equal.</summary>
		public override bool Equals(object obj)
		{
			if (obj == null) return false;
			return obj is Square && Equals((Square)obj);
		}

		/// <summary>Returns the hash code for this instance.</summary>
		public override int GetHashCode()
		{
			return Index;
		}
		/// <summary>Indicates whether two instances are equal.</summary>
		public static bool operator ==(Square left, Square right)
		{
			return left.Equals(right);
		}
		/// <summary>Indicates whether two instances are not equal.</summary>
		public static bool operator !=(Square left, Square right)
		{
			return !left.Equals(right);
		}

		#endregion

		#region ' Operators '

		public static implicit operator Square(byte index)
		{
			return new Square(index);
		}
		public static explicit operator Square(int index)
		{
			return new Square(index);
		}
		public static implicit operator int(Square square)
		{
			return square.Index;
		}
		public static implicit operator byte(Square square)
		{
			return square.Index;
		}

		#endregion

		#region ' Statics '

		static ReadOnlyCollection<Square> _all;

		/// <summary>Gets all 64 Squares on board</summary>
		public static ReadOnlyCollection<Square> All
		{
			get
			{
				return _all ?? (_all = new ReadOnlyCollection<Square>(
					Enumerable.Range(0, 128)
						.Where(i => (i & 0x88) == 0)
						.Select(i => new Square(i))
						.ToArray()));
			}
		}

		#endregion
	}
}